package com.distortiongames.level27.math
{
	/**
	 * Mersenne Twister seeded random algorithm implementation
	 * ported from C source originally coded by Takuji Nishimura and Makoto Matsumoto
	 *
	 * This software is provided 'as-is', without any express or implied warranty.
	 * In no event will the authors be held liable for any damages arising from the use of this software.
	 *
	 * This file is released under the LGPL "GNU Lesser General Public License"
	 * More information can be found here: {@link http://www.gnu.org/copyleft/lesser.html}
	 *
	 * @author     Jordi Boggiano <j.boggiano@seld.be>
	 * @copyright  2007 Jordi Boggiano
	 * @license    http://www.gnu.org/copyleft/lesser.html  GNU Lesser General Public License
	 * @link       http://seld.be/
	 * @version    1.0.0
	 * @date       2007-12-16
	 * @package    com.seld.util
	 * 
	 * @see http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/CODES/mt19937ar.c
	 * @see http://en.wikipedia.org/wiki/Mersenne_twister
	 */
 	public class SeededRand implements IRandGenerator
	{
		protected static const 
			N:int = 624,
			M:int = 397,
			MATRIX_A:uint = 0x9908b0df,
			UPPER_MASK:uint = 0x80000000,
			LOWER_MASK:uint = 0x7fffffff;

		protected var
			mt:Array = new Array(N),
			mti:int = N+1,
			mag01:Array = [0, MATRIX_A],
			s:uint,
			rangeStart:uint = 0,
			rangeEnd:uint = 0;

		/**
		 * constructor
		 * 
		 * @param seed the random seed
		 */
		public function SeededRand(seed:uint):void
		{
			initialize(seed);
			s = seed;
		}
		
		/**
		 * sets the range to be used
		 * 
		 * must be set before using 'ranged' to get random numbers
		 *
		 * @param min the range start
		 * @param max the range end
		 */
		public function setRange(min:uint, max:uint):void
		{
			if(min >= max)
				throw new Error('min can not be greater or equal to max');

			rangeStart = min;
			rangeEnd = max-min+1;
		}
		
		/**
		 * initializes mt[N] with a seed
		 * 
		 * @param seed the random seed
		 */
		protected function initialize(seed:uint):void
		{
			mt[0] = seed & 0xffffffff;
			for(mti=1; mti<N; mti++)
			{
				mt[mti] = (1812433253 * (mt[mti-1] ^ (mt[mti-1] >> 30)) + mti); 
		        mt[mti] &= 0xffffffff;
			}
		}

		/**
		 * generates a random number between 0 and 0xffffffff
		 * 
		 * @return random int number
		 */
		public function get int32():uint
		{
		    var y:uint;

			// generates N numbers at once every N numbers
		    if(mti >= N)
		    {
				var kk:int;
				
				for(kk=0;kk<N-M;kk++)
				{
		            y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
					mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1];
				}
				for (;kk<N-1;kk++)
				{
					y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
					mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1];
				}
				y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
				mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1];
				
				// resets counter
				mti = 0;
			}

			y = mt[mti++];
		
			// tempering
			y ^= (y >> 11);
			y ^= (y << 7) & 0x9d2c5680;
			y ^= (y << 15) & 0xefc60000;
			y ^= (y >> 18);

			return y;
		}

		/**
		 * generates a random number between 0 and 1
		 *
		 * takes an int32 random number and divides it by 2^(32-1),
		 * this might not be very right but it should still be acceptable 
		 * for most usages
		 *
		 * @return random float number
		 */
		public function get float():Number
		{
			return int32/2147483647;
		}
		
		/**
		 * generates a random number comprised in the user defined range
		 * 
		 * @return random int number
		 */
		public function get ranged():uint
		{
			return rangeStart + float * rangeEnd;
		}

		/**
		 * the seed set in the constructor, read-only
		 * 
		 * @return the seed used to generate the numbers
		 */
		public function get seed():uint
		{
			return s;
		}
		
		public function nextInt(max:uint, min:uint = 0):uint {
			setRange(min, max);
			return ranged;
		}
		
		public function nextBoolean(chance:Number = .5) : Boolean {
			return float < chance;
		}
	}
}